Kotlin的標準函數(或叫Scope functions)是指在Standard.kt中定義的函數,在任何Kotlin程式碼都可以自由呼叫他們:let、apply、with、also、run、takeIf和takeUnless等函數,適當使用這些函數可以幫助我們減少一些重複的程式碼。
基本上,這些函數的作用相同:在對象上執行一段程式碼。不同的是這個對象如何在作用域{}中變得可用,以及整個表達式的結果是什麼。
因為標準函數在本質上都非常相似,所以了解它們之間的差異很重要。每個標準函數之間有兩個主要區別:
引用上下文對象的方式。在標準函數的 lambda 內部,上下文對象可通過短引用而不是其實際名稱獲得。每個標準函數使用以下兩種方式之一來訪問對象:
返回值:
fun main(){
val username = "ironman"
// 不使用run,也許會這樣寫
val isIllegalName = isTooLong(username)
val result = check(isIllegalName)
print(result) //結果: 註冊成功
//使用run
username.run(::isTooLong)
.run(::check)
.run(::println)
//結果: 註冊成功
//使用run巢狀寫法
println(check(isTooLong(username))) //結果: 註冊成功
}
//檢查長度
fun isTooLong(name : String) = name.length > 10
//判斷要印出的訊息
fun check(isTooLong : Boolean): String {
return if (isTooLong) {
"名字太長啦!"
} else {
"註冊成功"
}
}
前面文章曾配合安全呼叫來檢處理可空類型,這也是官方指南(在文章最後)建議的功能。這裡簡單講一下它的特性:
//取numbers中元素的奇數平方值印出:
val numbers = listOf<Int>(1, 2, 3, 4, 5)
//使用的let的寫法:
numbers.filter { it % 2 == 1 }.map { it * it }.let{println(it)}
//不使用的let的寫法:
val resultList = numbers.filter { it % 2 == 1 }.map { it * it }
println(resultList)
如果let內只有一個用it作為參數的函數時,也可以使用::
引用方法,如: let{println(it)}可寫成let(::println)
//主建構函數預設0歲(這裡不討論胎兒)並居無定所
class Person(val name: String, var age: Int = 0, var neighborhood: String = "居無定所") {
//搬到哪去
fun moveTo(place: String) {
neighborhood = place
println("$age \b歲的$name \b搬到$neighborhood")
}
//長大了幾歲
fun grownUp(increase: Int) {
age += increase
}
}
fun main(){
//孟子沒人不知道吧,設定基本資料時,可以使用apply
val mencius = Person("孟子", 3 , "墳地附近")
//大家也都知道他媽媽出了名的愛搬家吧,年紀我亂掰的
//年紀 、住處資料變更時可以使用apply
mencius.apply {
println("$age \b歲的$name \b搬到$neighborhood")
grownUp(1)
moveTo("市集屠宰場附近")
grownUp(1)
moveTo("學堂旁")
}
//結果:3歲的孟子搬到墳地附近
// 4歲的孟子搬到市集屠宰場附近
// 5歲的孟子搬到學堂旁
}
不使用apply的話,就要一直喊孟子出來:
println("$age \b歲的$name \b住在$neighborhood")
mencius.grownUp(1)
mencius.moveTo("市集屠宰場附近")
mencius.grownUp(1)
mencius.moveTo("學堂旁")
//執行結果同上。
val numbers = listOf<Int>(1, 2, 3, 4, 5)
with(numbers){
println("numbers中有: $this")
println("numbers中共有 $size 個元素")
}
另一種用例是引入一個輔助對象,對象的屬性或函數可以用於計算出一個返回值
val numbers = listOf<Int>(1, 2, 3, 4, 5)
val result = with(numbers) {
//使用「+」運算子連接兩個句子
"numbers中第一個元素 : ${first()}\n" +
"numbers中最後一個元素 : ${last()}"
}
println(result)
//結果:
// numbers中第一個元素 : 1
// numbers中最後一個元素 : 5
因為with使用方法比較不同,而且還要注意對象可空的問題,所以好像比較少用(?)
fun main(){
val numbers = mutableListOf<Int>(1, 2, 3, 4, 5)
numbers.also { println(it) }.add(6)
//結果: [1, 2, 3, 4, 5]
}
takeIf函數會判斷lambda中提供的條件運算式,若為true就會返回接收者物件,false返回null。
下例是一個隨機數字如果是大於40的偶數才會印出:
val randomNum = (1..100).random()
//由於takeIf有可能返回null值,所以使用安全呼叫
randomNum.takeIf { it > 40 && it % 2 == 0 }?.let(::println)
takeIf的輔助性函數,兩者唯一區別是takeUnless是判斷給訂條件為false的時候才回返回原始接收者物件。如果是複雜的判斷建議還是使用takeIf比較好,語意上比較好讀不易混淆。
Function | Object reference | Return value | Is extension function |
---|---|---|---|
let | it | Lambda result | Yes |
run | this | Lambda result | Yes |
run | - | Lambda result | No: called without the context object |
with | this | Lambda result | No: called without the context object |
apply | this | Context object | Yes |
also | this | Context object | Yes |
takeIf | it | Context object/null | Yes |
takeUnless | it | Context object/null | Yes |
註 : run函數有另一個不常用的版本是不需要接收者,不傳遞接收者引數,沒有作用域限制,返回lambda值。
- 在非空對像上執行 lambda:let
- 在局部範圍內將表達式作為變數引入:let
- 對象配置:apply
- 對象配置和計算結果:run
- 在需要表達式的地方運行語句:非擴展函數版的run
- 附加效果:also
- 對對象進行分組函數調用:with
不同功能的用例重疊,因此您可以根據項目或團隊中使用的特定約定來選擇功能。
儘管標準函數是一種使代碼更簡潔的方法,但請避免過度使用它們:它會降低代碼的可讀性並導致錯誤。避免嵌套標準函數並在鏈接它們時要小心:很容易對當前上下文對象和this或it的值感到困惑。
參考
kotlin權威2.0
kotlin官方文檔
Khan Academy’s - Nice utility functions
明天見